/**
 * 
 */
package org.apache.asyncweb.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.asyncweb.server.HttpServerConfig.Service;
import org.apache.asyncweb.server.filter.GZipFilter;
import org.apache.asyncweb.server.resolver.PatternMatchResolver;
import org.apache.asyncweb.server.transport.mina.MinaTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 */
public class HttpServer implements Runnable {

	private static final Logger LOG = LoggerFactory.getLogger(HttpServer.class);

	private static final String HELP_ARG = "h";
	private static final String PORT_ARG = "port";
	private static final String FILE_ARG = "file";
	private static final String WEB_ARG = "web";

	private String filename;

	private String web;

	private String port;

	public HttpServer(String filename, String web, String port) {
		super();
		this.filename = filename;
		this.web = web;
		this.port = port;
	}

	public void run() {
		HttpServerConfig config = loadConfig(filename);
		if (config == null)
			return;
		BasicServiceContainer container = new BasicServiceContainer();

		MinaTransport transport = new MinaTransport();
		transport.setPort(config.getPort());
		transport.setBacklog(config.getBacklog());
		transport.setLogLevel(config.getLogLevel());
		transport.setProcessorCount(config.getProcessorCount());
		transport.setWorkThreads(config.getWorkThreads());
		container.addTransport(transport);

		HttpServiceHandler handler = new HttpServiceHandler();
		PatternMatchResolver resolver = new PatternMatchResolver();
		handler.setServiceResolver(resolver);
		if (config.getServices() != null) {
			Class<?> clazz;
			for (Service service : config.getServices()) {
				if (service.getPath() != null && service.getPath().length() > 0
						&& service.getName() != null
						&& service.getName().length() > 0
						&& service.getClazz() != null
						&& service.getClazz().length() > 0) {
					try {
						clazz = this.getClass().getClassLoader()
								.loadClass(service.getClazz());
						if (HttpService.class.isAssignableFrom(clazz)) {
							resolver.addPatternMapping(service.getPath(),
									service.getName());
							handler.addHttpService(service.getName(),
									(HttpService) clazz.newInstance());
						}
					} catch (Exception e) {
						LOG.error(e.getMessage(), e);
					}

				}
			}
		}
		container.addServiceFilter(handler);
		container.addServiceFilter(new GZipFilter());
		try {
			container.start();
		} catch (ContainerLifecycleException e) {
			LOG.error(e.getMessage(), e);
		}
	}

	protected HttpServerConfig loadConfig(String filename) {
		HttpServerConfig config = null;

		// find configuration file
		if (filename == null)
			filename = "wolf.xml";
		InputStream input = null;
		File file = new File(filename);
		if (!file.exists()) {
			input = this.getClass().getClassLoader()
					.getResourceAsStream(filename);
		} else {
			try {
				input = new FileInputStream(file);
			} catch (FileNotFoundException e) {
			}
		}
		if (input == null) {
			LOG.error("File {} is not found.", filename);
			return null;
		}
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(input);
			Node root = document.getFirstChild();
			NodeList nodes = root.getChildNodes();
			Node node;
			Element element;
			for (int i = 0; i < nodes.getLength(); i++) {
				node = nodes.item(i);
				if (node instanceof Element && node.getNodeName().equals("web")) {
					element = (Element) node;
					if (web.equals(element.getAttribute("id"))) {
						config = parseWeb(element);
						break;
					}
				}
			}
			if (config == null) {
				LOG.error("web {} is not found in file", web);
				return null;
			}
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			return null;
		} finally {
			try {
				input.close();
			} catch (IOException e) {
			}
		}
		return config;
	}

	private HttpServerConfig parseWeb(Element webNode) {
		if (webNode == null)
			return null;
		HttpServerConfig config = new HttpServerConfig();
		config.setId(webNode.getAttribute("id"));
		if (port == null) {
			if (webNode.hasAttribute("port"))
				config.setPort(Integer.valueOf(webNode.getAttribute("port")));
		} else
			config.setPort(Integer.valueOf(port));
		if (webNode.hasAttribute("backlog"))
			config.setBacklog(Integer.valueOf(webNode.getAttribute("backlog")));
		if (webNode.hasAttribute("processorCount"))
			config.setProcessorCount(Integer.valueOf(webNode
					.getAttribute("processorCount")));
		if (webNode.hasAttribute("workThreads"))
			config.setWorkThreads(Integer.valueOf(webNode
					.getAttribute("workThreads")));
		if (webNode.hasAttribute("logLevel"))
			config.setLogLevel(webNode.getAttribute("logLevel"));
		NodeList nodes = webNode.getElementsByTagName("services");
		if (nodes != null) {
			NodeList serviceNodes;
			Element serviceNode;
			Service service;
			for (int i = 0; i < nodes.getLength(); i++) {
				serviceNodes = ((Element) nodes.item(i))
						.getElementsByTagName("service");
				if (serviceNodes != null) {
					config.setServices(new ArrayList<Service>());
					for (int j = 0; j < serviceNodes.getLength(); j++) {
						serviceNode = (Element) serviceNodes.item(j);
						service = new Service();
						service.setPath(serviceNode.getAttribute("path"));
						service.setName(serviceNode.getAttribute("name"));
						service.setClazz(serviceNode.getAttribute("class"));
						config.getServices().add(service);
					}
				}
			}
		}
		return config;
	}

	public static void main(String[] args) {
		// analyze arguments
		Map<String, String> map = new HashMap<String, String>();
		if (args != null && args.length > 0) {
			String arg;
			String key;
			for (int i = 0; i < args.length; i++) {
				arg = args[i];
				if (arg.length() > 1 && arg.charAt(0) == '-') {
					key = arg.substring(1).toLowerCase();
					if (key.equals(HELP_ARG))
						help();
					else {
						if (++i < args.length)
							map.put(key, args[i]);
						else {
							help();
							return;
						}
					}
				}
			}
		}
		String web = map.get(WEB_ARG);
		if (web == null) {
			help();
			return;
		}
		String file = map.get(FILE_ARG);
		String port = map.get(PORT_ARG);
		HttpServer server = new HttpServer(file, web, port);
		server.run();
	}

	private static void help() {
		System.out
				.print("arguments: [-file file] [-web web] [-port port] [-h]");
	}

}
